package org.cocopapaya.anttsp.domain;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;

import org.apache.commons.collections15.Factory;
import org.cocopapaya.common.flowcontrol.NonIndexedLoop;

import edu.uci.ics.jung.algorithms.generators.GraphGenerator;
import edu.uci.ics.jung.algorithms.generators.Lattice2DGenerator;
import edu.uci.ics.jung.algorithms.generators.random.BarabasiAlbertGenerator;
import edu.uci.ics.jung.algorithms.generators.random.EppsteinPowerLawGenerator;
import edu.uci.ics.jung.algorithms.generators.random.KleinbergSmallWorldGenerator;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.SparseMultigraph;
import edu.uci.ics.jung.graph.util.EdgeType;

public class AntTspSimulation {

	private Graph<City, Path> graph;

	private List<Ant> ants = new ArrayList<Ant>();;

	private QuantityField field;

	private int size = 3;

	public AntTspSimulation() {

		instantiateObjects();

		initializeTour();

		initializeField();

	}

	private void initializeField() {
		this.field.initialize();
	}

	private void initializeTour() {

		Collection<City> generatedCities = this.graph.getVertices();

		for (City c : generatedCities) {
			c.clear();
		}

		Queue<City> visitBacklog = new ArrayBlockingQueue<City>(generatedCities
				.size());
		visitBacklog.addAll(generatedCities);

		for (Ant a : this.ants) {
			a.prepareForTour(visitBacklog.poll(), generatedCities);
		}

	}

	private void instantiateObjects() {

		Factory<Path> pathFactory = new Factory<Path>() {

			public Path create() {

				return new Path();
			}

		};

		Factory<Graph<City, Path>> graphFactory = new MyGraphFactory();

		Factory<City> cityFactory = new MyCityFactory(graphFactory);

		Lattice2DGenerator<City, Path> generator = new Lattice2DGenerator<City, Path>(
				graphFactory, cityFactory, pathFactory, this.size, false);

		this.graph = generator.create();

		Collection<City> generatedCities = this.graph.getVertices();

		for (City c : generatedCities) {
			ants.add(new Ant());
		}

		this.field = new QuantityField(this.ants, this.graph.getEdges());

	}

	public Graph<City, Path> getGraph() {
		return this.graph;
	}

	public void runSteps(int steps) {

		boolean allDone = false;
		initializeTour();

		for (int i = 0; i < steps; i++) {

			// if all ants are done, let's start a new tour
			if (allDone) {
				initializeTour();
				
			}
			// in this step, all ants with work to do move do one visit
			for (Ant ant : ants) {
				if (ant.hasNext()) {
					ant.visitNext();
					allDone = false;
				} else {
					allDone = true;
				}

			}
			if (allDone) {

				field.updateQuantities();

				sanityCheck();
			}
		}

	}

	public void runTours(int steps) {

		System.out.println("Running with " + steps);

		// must have look at this algorithm. in this implementation each ant
		// will run a complete tour before
		// the next ant runs. this means that during the tour the early ants
		// have no information about the latter. is this a problem?

		NonIndexedLoop loop = new NonIndexedLoop(steps) {

			public void runInside() {

				initializeTour();
				// let all ants update the path they are at
				for (Ant ant : ants) {
					ant.runTour();

				}
				// update the field here
				field.updateQuantities();

				sanityCheck();

			}

		};

		loop.run();

	}

	public Tour getShortestTour() {

		Tour shortest = null;

		for (Ant a : ants) {
			Tour t = a.getTour();

			if (shortest != null) {
				if (t.shorterThan(shortest)) {
					shortest = t;
				}

			} else {
				shortest = t;
			}

		}

		return shortest;
	}

	private class MyCityFactory implements Factory<City> {

		private Factory<Graph<City, Path>> graphFactory;

		public MyCityFactory(Factory<Graph<City, Path>> graphFactory) {
			this.graphFactory = graphFactory;
		}

		public City create() {
			// TODO Auto-generated method stub
			return new City(graphFactory.create());
		}

	}

	private class MyGraphFactory implements Factory<Graph<City, Path>> {

		private Graph<City, Path> theGraph = new SparseMultigraph<City, Path>();

		public Graph<City, Path> create() {
			return theGraph;
		}

	}

	private void sanityCheck() {

		// verify cities
		int worldPopulation = 0;

		for (City c : this.graph.getVertices()) {
			worldPopulation += c.countInhabitants();
		}

		if (worldPopulation != this.ants.size()) {
			throw new RuntimeException("sum of inhabitants of cities "
					+ worldPopulation + " != all ants " + this.ants.size());
		}

	}

	public Ant getCameraAnt() {
		return this.ants.get(3);
	}

}
